決定木(Decision Tree)
Overview
決定木は、クラス分類と回帰タスクに広く用いられているモデルです。決定木では、True/Falseで答えられる質問で構成された階層的な木構造を学習します。決定木における学習は、正解に最も早く終端ノード(葉 (leaf))にたどり着けるような一連のTrue/False型の質問学習を意味します。また、これらの質問はテスト(test)と呼ばれます。
https://gyazo.com/a5e3aaf6137d2ff67168073cda67daec
Theory
決定木を連続値のデータセットに用いるときは、「特徴量$ iは値$ aよりも大きいか?」という形を取ります。このプロセスを再帰的に繰り返すと2分木による決定木が得られます。ただし、分割をし過ぎると、モデルは複雑になりすぎ、訓練データに対して大幅に過剰適合してしまうので注意が必要です。
https://gyazo.com/c2044796fb241bab06d1601cf6ccf17d
https://gyazo.com/3d90420ba423fc5411f437aebfdacc24
https://gyazo.com/ef5d30263b5ce301e9eee46a6e7e23ed
Coding(Classification)
cancerデータセットでモデルを構築・学習・評価する
code: Python
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_breast_cancer
cancer = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(cancer.data, cancer.target, stratify=cancer.target, random_state=42)
tree = DecisionTreeClassifier(random_state=0)
tree.fit(X_train, y_train)
print('Accuracy on training set: {:.3f}'.format(tree.score(X_train, y_train)))
print('Accuracy on test set: {:.3f}'.format(tree.score(X_test, y_test)))
--------------------------------------------------------------------------
Accuracy on training set: 1.000
Accuracy on test set: 0.937
--------------------------------------------------------------------------
このように、決定木の深さに制約を与えないと、決定木はいくらでも深く、複雑になります。したがってこのような決定木は過剰適合しやすくなります。
過剰適合を防ぐには2つ方法があります。構築過程で木の生成を早めに止める事前枝刈り(pre-pruning)と、一度木を構築してから、情報の少ないノードを削除する事後枝刈り(post-pruning)(ただの枝刈り(pruning)とも呼ばれる)です。scikit-learnには、事前枝刈りしか実装されていません。
cancerデータセットで事前枝刈りしてモデルを構築・学習・評価する
code: Python
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_breast_cancer
cancer = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(cancer.data, cancer.target, stratify=cancer.target, random_state=42)
tree = DecisionTreeClassifier(max_depth=4, random_state=0)
tree.fit(X_train, y_train)
print('Accuracy on training set: {:.3f}'.format(tree.score(X_train, y_train)))
print('Accuracy on test set: {:.3f}'.format(tree.score(X_test, y_test)))
--------------------------------------------------------------------------
Accuracy on training set: 0.988
Accuracy on test set: 0.951
--------------------------------------------------------------------------
Coding(Regression)
決定木によるモデルを回帰に使う際には、外挿(extrapolate)ができない、つまり訓練データのレンジの外側に対しては予測ができないので注意する必要があります。
RAM価格データセットでモデルを構築・学習・評価・可視化して線形回帰と比較する
code: Python
import matplotlib.pyplot as plt
import mglearn
import os
from sklearn.tree import DecisionTreeRegressor
from sklearn.linear_model import LinearRegression
ram_prices = pd.read_csv(os.path.join(mglearn.datasets.DATA_PATH, 'ram_price.csv'))
# 2000年以前のデータで学習して2000年以降の価格を予測
# 日付に基づいて価格を予測
# データとターゲットの関係を単純にするため対数変換
y_train = np.log(data_train.price)
# モデルに訓練データをフィットさせる
tree = DecisionTreeRegressor().fit(X_train, y_train)
linear_reg = LinearRegression().fit(X_train, y_train)
# 2000年以降も含めた全てのデータポイントに対して予測を行う
pred_tree = tree.predict(X_all)
pred_lr = linear_reg.predict(X_all)
price_tree = np.exp(pred_tree)
price_lr = np.exp(pred_lr)
## プロット
plt.semilogy(data_train.date, data_train.price, label = 'Training data')
plt.semilogy(data_test.date, data_test.price, label = 'Test data')
plt.semilogy(ram_prices.date, price_tree, label = 'Tree prediction')
plt.semilogy(ram_prices.date, price_lr, label = 'Linear prediction')
plt.legend()
plt.show()
https://gyazo.com/f6e37628c456fc40e7cdb4f596719d0b
このように、決定木による回帰モデルは、訓練データにない領域に対しては、決定木の知る最後の点を返します。
Advanced
決定木の解析
treeモジュールのexport_graphviz関数を用いると決定木を可視化することができます。この関数は、グラフを格納するテキストファイル形式である.dotファイル形式でファイルを書き出します。以下は、Coding(Classification)で使用したtreeモジュールを可視化する例です。
code: Python
from sklearn.tree import export_graphviz
import graphviz
export_graphviz(tree, out_file='tree.dot', class_names='malight', 'benign', feature_names=cancer.feature_names, impurity=False, filled=True) with open('tree.dot') as f:
dot_graph = f.read()
graphviz.Source(dot_graph)
https://gyazo.com/368ce84a052e00982ed6a346472bfce5
可視化した木は多くの情報を含むので、特に大部分のデータが通るパスはどこかに注目すると良いです。samplesは通るデータ数、valueは良性、悪性などを示します。2階層目のtexture error <= 0.473では、ほとんど値が良性となっており、数少ない悪性のものをさらに分割するために階層を深くしていることがわかります。
決定木の特徴量の重要性
決定木全体を見るのは大変なので、決定木の挙動を要約する特性値を見ます。要約に最も使われるのは、特徴量の重要度(feature importance)と呼ばれる、決定木が行う判断にとって、個々の特徴量がどの程度重要かを示す割合です。それぞれの特徴量に対して、0は「まったく使われていない」、1は「完全にターゲットを予想できる」を意味します。特徴量の重要度の和は常に1になります。
code: Python
print('Feature importances:\n{}'.format(tree.feature_importances_))
def plot_feature_importances_cancer(model):
n_features = cancer.data.shape1 plt.barh(range(n_features), model.feature_importances_, align='center')
plt.yticks(np.arange(n_features), cancer.feature_names)
plt.xlabel('Feature importance')
plt.ylabel('Feature')
plot_feature_importances_cancer(tree)
--------------------------------------------------------------------------
Feature importances:
[0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0.01019737 0.04839825
0. 0. 0.0024156 0. 0. 0.
0. 0. 0.72682851 0.0458159 0. 0.
0.0141577 0. 0.018188 0.1221132 0.01188548 0. ]
--------------------------------------------------------------------------
https://gyazo.com/8fb17beb0284d7eae9b1964e0c96bc21
上図から、「worst radius」が群を抜いて重要だということがわかります。これは、決定木を解析した際の、最初の階層で2つのクラスがきれいに分離できているという観察結果と一致します。
しかし、ある特徴量の重要度の値が低いからといって、その特徴量の持つ情報量が少ないとは限りません。単にその決定木で採用されなかった、というだけです。
また、特徴量の重要度は、サンプルが良性か悪性かを判断する上で、「worst radius」が重要だということを教えてくれますが、この値が大きいと良性になるのか悪性になるのかを教えてくれるわけではありません。
Summary
Merit
結果のモデルが容易に可視化できる
小さい決定木であれば専門家でなくても理解可能である
特徴量ごとにスケールが大きく異なるような場合でも、2値特徴量と連続値特徴量が混ざっているような場合でも、問題なく機能する(正規化や標準化が必要ない)
Demerit
Parameters
max_depth
max_leaf_nodes
min_samples_leaf